【经典】"有格调"的MCU初始化(绝对要get)
1、简单聊一聊
2、初始化我们做些什么事情
大家平时进行软件开发基本上都会有一个初始化过程(如果没有的话我就不知道说什么好了,我就当做你有奥),我们的硬件寄存器、模块的配置、软件数据结构、系统初始状态等等都是需要我们进行设置的,以便于把控系统的初始运行状态,那么初始化过程中一般会进行哪些工作呢?
1)初始化延时一段时间
大家应该在较多工程代码初始化阶段都会看到一个Delay_ms(XXX);的语句,有些小伙伴可能没怎么在意,延时就延时呗,有什么大惊小怪的;有些小伙伴认为是防止MCU跑飞等等之类的原因。其实这个语句也是需要根据具体情况来进行编写的,比如之前就有一个项目需要快速启动,那么初始化延时就是不可取的。
1//伪代码
2#define SYS_INITIAL_DELY (200)
3void SystemInitial(void)
4{
5 Delay_ms(SYS_INITIAL_DELY); //系统延时
6 sLowInitial(); //底层初始化
7 sFucInitial(); //功能初始化
8 //......
9}
10void main(void) {
11
12 SystemInitial(); //系统初始化
13 //......
14}
Delay一下的原因:
首先聊聊对于等待MCU电源稳定防止跑飞,这个原因有点牵强,因为Delay函数也是一种正常程序运行,如果MCU跑飞Delay函数也是无法缓解的,如果改成防止外设初始化失败可能还说得过去,毕竟对于MCU内部外设功能的初始化有时候需要一个稳定的电源,否则容易产生初始化不成功等等异常。
不过根据作者之前MCU开发经验来看,主要是MCU与其他模块对接时候的上电运行门限存在差异,我们都知道大部分芯片都会有一个最低上电启动电压,电压太低芯片无法启动,电压如果在最低电压附近波动容易造成系统不稳定,如果外部芯片与MCU共用同一个电源,MCU上电运行电压门限低,而外部芯片门限高,当MCU已经开始进行配置外部芯片操作,而外部芯片才刚刚开始进入运行初始化,这样会导致MCU对外部芯片配置不成功等问题。
2) 比较传统的初始化结构图
大家别小看一个简单的初始化工作,一个结构清晰,层次分明的初始化函数封装能够为你在以后的大工程项目中省下不少时间,还记得刚写代码的时候,那变量初始化一个乱字都形容不过来,往往前面刚赋值接下来又被清零了,下面为大家提供一个简单传统的MCU初始化层次结构图,大家也可以结合自身项目进行设计:
解释一下:
上面的结构图采用自底向上的初始化层次,先进行最基础的系统时钟和总线时钟的配置,以及一些CPU相关的底层初始化;对于MCU次底层的是各个外设的初始化比如UART、USB、SDIO等等;然后对于数据结构和模型初始化可以根据具体功能模块进行划分,最后是对EEPROM、FLASH等存储信息进行读取以恢复系统之前的默认参数和状态。
上面大家可以根据自身项目具体情况进行设计,比如含有RTOS、GUI等等组件初始化也是需要进行划分与归类的,这里作者仅仅提供思路。
3、让MCU告诉你更多
其实大部分小伙伴进行MCU软件开发的时候对其并不是特别的熟悉,而且确实现在很多MCU都封装得特别好,比如MCU具体如何进行运算,在哪里取数据,又把数据送去哪里,一段代码会运算多长时间等等,导致一些小伙伴遇到一些bug难以解决,所以如果我们能够更清楚的了解一款MCU那么就多一分对bug的把控能力,一定要我们玩MCU,而不要被MCU玩弄。
好了,为了能够更好的把控MCU,我们得在今天的初始化主题这里干点事。
1)直接初始化输出基础类型大小
1void PrintTypeSize(void)
2{
3 printf("Type Size:\n");
4 printf("char : %u\n", sizeof(char));
5 printf("short : %u\n", sizeof(short));
6 printf("int : %u\n", sizeof(int));
7 printf("long : %u\n", sizeof(long));
8 printf("long long : %u\n", sizeof(long long));
9 printf("void* : %u\n", sizeof(void *));
10 printf("float : %u\n", sizeof(float));
11 printf("double : %u\n", sizeof(double));
12 //......Add your printf of your think
13}
解释一下:
大家觉得这一段代码很简单吧,估计有很多小伙伴都没有真正在自己所用的平台上打印过吧,大伙在学习C语言的过程中应该都知道这中间有些数据类型是与平台相关的,如果我们没有对这些基础类型的长度把控好,估计bug就来找你的.
同时大家在平时的编程过程中也一定要注意数据类型的范围,引起的后果可大可小哦,同时对于数据类型的深入学习可以参考往期文章。
2)字节对齐与顺序
这一部分算是作者开了几篇文章好好说过的话题了,也是非常重要的内容,这里不做过多的解释,相关的检测算法也在其中为大家展示了,也是比较简单的,所以大家如果不想查看MCU手册的时候直接可以使用对应的函数输出对应的结构体默认的对齐方式,字节序(也就是我们带小端)等等属性,所以作者这里建议大家在研发前期初始化过程中打印相关内容。
一些小伙伴遇到一些小问题就会到处查资料,其实有些答案可以通过我们的编程让MCU自己告诉我们,这也是我们接触一款新的MCU进行开发的基础。
3) 运算时间的把控
其实这里才是本篇文章的重点,也是作者非常想推荐给大家的一种评估和熟悉MCU性能方法,非常经典的宏定义,下面是作者在Windows下面模拟的程序,大家可以改造到自己的MCU上运行测试:
1#include <stdio.h>
2#include <stdlib.h>
3#include <windows.h>
4#include <math.h>
5/***************************************************
6 * Fuction: 10次运行Operation
7 * Author :(公众号:最后一个bug)
8 ***************************************************/
9#define TEN_OPERATE_TIMES(x) do { x; x; x; x; x; x; x; x; x; x; } while (0)
10/***************************************************
11 * Fuction: 50次运行Operation
12 * Author :(公众号:最后一个bug)
13 ***************************************************/
14#define FIFTY_OPERATE_TIMES(x) do {\
15 TEN_OPERATE_TIMES(x); \
16 TEN_OPERATE_TIMES(x); \
17 TEN_OPERATE_TIMES(x); \
18 TEN_OPERATE_TIMES(x); \
19 TEN_OPERATE_TIMES(x); \
20 } while (0)
21/***************************************************
22 * Fuction: 答应对应运算op所需时间
23 * Author :(公众号:最后一个bug)
24 ***************************************************/
25#define PRINT_TIME(name, operate, count) do { \
26 unsigned char i = 0;\
27 LARGE_INTEGER StartTime;\
28 LARGE_INTEGER EndTime;\
29 LARGE_INTEGER Freq;\
30 QueryPerformanceFrequency (&Freq);\
31 QueryPerformanceCounter(&StartTime);\
32 for (i = 0; i < count; i++) { \
33 FIFTY_OPERATE_TIMES(operate); \
34 } \
35 QueryPerformanceCounter(&EndTime);\
36 printf("%-8s %7.8f ms\n", name, (EndTime.QuadPart -StartTime.QuadPart )*1000.0f*1000.0f/Freq.QuadPart/ (double)(count * 50.0));\
37 } while (0)
38
39
40/***************************************************
41 * Fuction: 变量定义区域
42 * Author :(公众号:最后一个bug)
43 ***************************************************/
44volatile float fv = 1.0;
45volatile float fv_out = 0.8;
46/***************************************************
47 * Fuction: 测试main函数
48 * Author :(公众号:最后一个bug)
49 ***************************************************/
50int main(int argc, char *argv[]) {
51
52 PRINT_TIME("Mul :", fv_out *= fv, 200);
53 PRINT_TIME("Div :", fv_out /= fv, 200);
54 PRINT_TIME("sin(x):", fv_out = sinf(fv), 100);
55 PRINT_TIME("cos(x):", fv_out = cosf(fv), 100);
56 printf("\n欢迎关注公众号:最后一个bug\n");
57 return 0;
58}
运行结果看一看:
解析一下:
首先这里主要是宏定义和do{}while(0);的结合使用案例,大家可以学习一下这种定义方式。
设计亮点 : 前面两个宏定义的设计大家应该会有疑问为什么不在for循环里面直接cnt累计时间,最后累计好足够的时间不就可以计算每次运算的时间了吗 ? 大家需要注意的是for循环也是由几条汇编语句构成的,如果所测试的运算操作时间非常短,测试的最小时间也不会小于for运算的时间,这样测量误差较大,所以这里构造了多次运算,从而与for语句运算时间不在一个运行时间等级上便可以提高精度。估计很多小伙伴听得迷迷糊糊,好了,上图!!!!
4、最后小结
这几天挺忙的,作者也是徜徉在bug之中,也真希望在最后一个bug处歇歇脚,这篇文章算是作者每天一小段一小段的写出来了的,希望对大家有帮助。
好了,这里是公众号:“最后一个bug”,一个为大家打造的技术知识提升基地。同时非常感谢各位小伙伴的支持,我们下期精彩见!
推荐好文 点击蓝色字体即可跳转